<?php
/**
 * Spt_View class
 * 
 * Copyright (c) 2008, Damien Lebrun
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Damien Lebrun nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY DAMIEN LEBRUN ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL DAMIEN LEBRUN BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @category   Zend
 * @package    Spt_View
 * @copyright  Copyright (c) 2008 Damien Lebrun
 * @license    New BSD License
 * @author     Damien Lebrun
 */

include_once 'Zend/View/Abstract.php';


/**
 * Unlike with Zend_View, the variables used in the template are escaped by default.
 * Escaping or not variables is done at the controller level.
 * <code>
 * $view = new Spt_View();
 * // will be escaped
 * $view->foo = $_GET['foo'];
 * $view->assign('foo', $_GET['foo'], true);
 * 
 * // won't be escaped (unless the template escape it by itself)
 * $view->assign('title', '<h1>Hello world!</h1>', false);
 * 
 * </code>
 * 
 * @category   Zend
 * @package    Spt_View
 */
class Spt_View extends Zend_View_Abstract
{

    /**
     * Variables to use in the templates
     * @var array
     */
    private $_data = array();

    /**
     * Constructor - register the view stream wrapper
     *
     * @param array $config
     */
    public function __construct($config = array())
    {
        if (! in_array(Spt_Stream_View::PROTOCOL, stream_get_wrappers())) {
            include_once 'Spt/Stream/View.php';
            stream_wrapper_register(Spt_Stream_View::PROTOCOL, 'Spt_Stream_View');
        }
        
        parent::__construct($config);
    }

    /**
     * 
     * Return variables and prevent E_NOTICE for nonexistent values
     *
     * If {@link strictVars()} is on, raises a notice.
     *
     * @param  string $key
     * @return null
     */
    public function __get($key)
    {
        if (array_key_exists($key, $this->_data)) {
            return $this->getVar($key);
        }
        
        if ($this->_strictVars) {
            trigger_error('Key "' . $key . '" does not exist', E_USER_NOTICE);
        }
        
        return null;
    }

    /**
     * Allows testing with empty() and isset() to work inside
     * templates.
     *
     * @param  string $key
     * @return boolean
     */
    public function __isset($key)
    {
        if ('_' != substr($key, 0, 1)) {
            return (array_key_exists($key, $this->_data) || isset($this->$key));
        }
        
        return false;
    }

    /**
     * Save variables for the view script(s).
     *
     * Checks first to ensure that the caller is not attempting to set a
     * protected or private member (by checking for a prefixed underscore)
     * and raised an exception if it doas.
     *
     * @param string $key The variable name.
     * @param mixed $val The variable value.
     * @return void
     * @throws Zend_View_Exception if an attempt to set a private or protected
     * member is detected
     */
    public function __set($key, $val)
    {
        if ('_' != substr($key, 0, 1)) {
            $this->assign($key, $val);
            return;
        }
        
        require_once 'Zend/View/Exception.php';
        throw new Zend_View_Exception('Setting private or protected class members is not allowed', $this);
    }

    /**
     * Allows unset() on object properties to work
     *
     * @param string $key
     * @return void
     */
    public function __unset($key)
    {
        if ('_' != substr($key, 0, 1)) {
            if (array_key_exists($key, $this->_data)) {
                unset($this->_data);
            } elseif (isset($this->$key)) {
                unset($this->$key);
            }
        }
    }

    
    /**
     * @see Zend_View_Abstract::assign()
     *
     * @param string|array $spec
     * @param mixed $value
     * @return Zend_View_Abstract
     */
    public function assign($spec, $value = null, $escape = true)
    {
        $error = false;
        // which strategy to use?
        if (is_string($spec)) {
            // assign by name and value
            if ('_' == substr($spec, 0, 1)) {
                $error = true;
            }
            
            $this->_data[$spec]['value'] = $value;
            $this->_data[$spec]['escape'] = $escape;
        
        } elseif (is_array($spec)) {
            // assign from associative array
            foreach ($spec as $key => $val) {
                if ('_' == substr($key, 0, 1)) {
                    $error = true;
                    break;
                }
                $this->_data[$key]['value'] = $val;
                $this->_data[$key]['escape'] = $escape;
            }
        } else {
            require_once 'Zend/View/Exception.php';
            throw new Zend_View_Exception('assign() expects a string or array, received ' . gettype($spec), $this);
        }
        
        if ($error) {
            require_once 'Zend/View/Exception.php';
            throw new Zend_View_Exception('Setting private or protected class members is not allowed', $this);
        }
        
        return $this;
    }

    /**
     * @see Zend_View_Abstract::clearVars()
     *
     */
    public function clearVars()
    {
        $this->_data = array();
    }

    /**
     * Return an assigned variable
     *
     * @param string $name
     * @param bool $escape Force value to be escaped
     * @return mixed
     */
    public function getVar($name, $escape = false)
    {
        if (! array_key_exists($name, $this->_data)) {
            return null;
        }
        
        $var = $this->_data[$name]['value'];
        
        if ($escape || $this->_data[$name]['escape']) {
            $var = $this->escape($var);
        }
        
        return $var;
    }

    /**
     * @see Zend_View_Abstract::getVars()
     *
     * @return array
     */
    public function getVars()
    {
        $return = array();
        $names = array_keys($this->_data);
        
        foreach ($names as $name) {
            $return[$name] = $this->getVar($name);
        }
        
        return $return;
    }

    /**
     * Escape string, array and object values
     * @see Zend_View_Abstract::escape()
     *
     * @param mixed $var
     * @return mixed
     */
    public function escape($var)
    {
        if (is_array($var) || is_object($var)) {
            //escape each array values
            foreach ($var as $key => $val) {
                $var[$key] = $this->escape($val);
            }
        } elseif (is_object($var)) {
            //escape each object values
            $var = clone ($var);
            if (is_a($var, 'Countable')) {
                foreach ($var as $key => $val) {
                    $var->$key = $this->escape($val);
                }
            } else {
                $properties = get_object_vars($this);
                foreach ($properties as $key => $val) {
                    $var->$key = $this->escape($val);
                }
            }
        } else {
            //escape others
            $var = parent::escape($var);
        }
        return $var;
    }

    /**
     * Includes the view script in a scope with only public $this variables.
     *
     * @param string $script The view script to execute.
     */
    protected function _run()
    {
        include Spt_Stream_View::PROTOCOL . '://' . func_get_arg(0);
    }
}
